Trajectory Divergence¶
from IPython.display import display
import math
import random
import pickle as pkl
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
import plotly.offline as pyo
pyo.init_notebook_mode()
Trajectory data is saved as a pandas dataframe in a .pkl file. We can also calculate mean trajectories for each clip and add it to the dataframe. Let’s import it and display the dataframe and its clip properties.
with open('trajectories.pkl', 'rb') as f:
data = pkl.load(f)
traj_df = data['traj_df'] # pandas dataframe with 3D coordinates, time, particpant id (pid), clip id (clip) and clip name as columns
clip_len = data['clip_len'] # array consisting number of time points indexed by clip_id
# calculate the mean trajectories
traj_df[['mean_x', 'mean_y', 'mean_z']] = traj_df.drop(columns='pid').groupby(['clip', 'time']).transform('mean')
# display df
display(traj_df)
# print unique clip names and lengths
print('Clip ID Clip Name Clip Length')
unique_clip_names = traj_df.clip_name.unique()
for i in range(len(clip_len)):
print(f'{str(i):10}{unique_clip_names[i]:14}{clip_len[i]}')
| x | y | z | time | pid | clip | clip_name | mean_x | mean_y | mean_z | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -0.068375 | 0.292656 | 0.076036 | 0 | 1 | 0 | testretest | -0.535419 | -0.584089 | 0.527666 |
| 1 | -0.560828 | 0.290854 | 0.095379 | 1 | 1 | 0 | testretest | -0.828858 | -1.073595 | 0.652328 |
| 2 | 0.248541 | -0.024260 | -0.019393 | 2 | 1 | 0 | testretest | -0.948996 | -1.365094 | 0.686838 |
| 3 | -0.021169 | 0.253559 | 1.618106 | 3 | 1 | 0 | testretest | -1.016553 | -1.504698 | 0.718371 |
| 4 | -0.218407 | 0.420255 | 2.193483 | 4 | 1 | 0 | testretest | -1.068053 | -1.757247 | 0.923804 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 245399 | -6.074567 | -14.429848 | 13.255661 | 251 | 76 | 14 | starwars | 7.020762 | -20.638204 | 2.571888 |
| 245400 | -5.333982 | -15.487228 | 15.741982 | 252 | 76 | 14 | starwars | 6.981013 | -20.744694 | 2.663826 |
| 245401 | -5.229411 | -14.917367 | 16.472929 | 253 | 76 | 14 | starwars | 7.065092 | -20.584518 | 2.731824 |
| 245402 | -4.298551 | -12.905822 | 15.564515 | 254 | 76 | 14 | starwars | 7.200529 | -20.280288 | 2.620943 |
| 245403 | -3.862932 | -14.278425 | 16.305193 | 255 | 76 | 14 | starwars | 7.261687 | -20.207359 | 2.578671 |
245404 rows × 10 columns
Clip ID Clip Name Clip Length
0 testretest 84
1 twomen 245
2 bridgeville 222
3 pockets 189
4 overcome 65
5 inception 227
6 socialnet 260
7 oceans 250
8 flower 181
9 hotel 186
10 garden 205
11 dreary 143
12 homealone 233
13 brokovich 231
14 starwars 256
Let’s plot the mean trajectory for each clip, averaged across all participants.
plotly_data = []
colorscale = ['#FB001C','#1C16FF','#FF0DE0','#74B741','#F58800','#0D6622','#5A3580','#970047','#CB0AFB','#957000','#FD0D89','#F78FDF','#71949D','#668BEC','#6D424B']
for clip, clip_name in enumerate(traj_df.clip_name.unique()):
# mean trajectories
temp_df = traj_df[(traj_df.clip_name==clip_name) & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
x=temp_df['mean_x'],
y=temp_df['mean_y'],
z=temp_df['mean_z'],
customdata=temp_df['time'],
mode='markers+lines',
marker={'size':2, 'color': colorscale[clip]},
line={'width':4, 'color': colorscale[clip]},
name=clip_name,
hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}')
plotly_data.append(mean_traj)
# formatting
plotly_layout = go.Layout(showlegend=True,
autosize=False,
width=800,
height=800,
margin={'l':0, 'r':0, 't':40, 'b':0},
legend={'orientation':'h',
'itemsizing':'constant',
'xanchor':'center',
'yanchor':'bottom',
'y':-0.1,
'x':0.5},
title={'text':'Mean Trajectories for Each Clip',
'xanchor':'center',
'yanchor':'top',
'x':0.5,
'y':0.98})
plotly_config = {'displaylogo':False,
'modeBarButtonsToRemove': ['resetCameraLastSave3d','orbitRotation']}
fig_traj = go.Figure(data=plotly_data, layout=plotly_layout)
fig_traj.show(config=plotly_config)
Let’s look at two clips have similar mean trajectories, “ocean” and “brokovich” in this case. We’ll plot their mean trajectories (represented as points connected by lines) as well as all of their individual trajectories (represented as individual points).
plotly_data = []
# OCEANS
## mean trajectory
temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
x=temp_df['mean_x'],
y=temp_df['mean_y'],
z=temp_df['mean_z'],
customdata=temp_df['time'],
mode='markers+lines',
marker={'size':2, 'color':'blue'},
line={'width':4, 'color':'blue'},
name='oceans',
legendgroup='oceans',
hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}')
plotly_data.append(mean_traj)
## individual trajectories
for pid in traj_df[traj_df.clip_name=='oceans'].pid.unique():
temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==pid)]
pid_traj = go.Scatter3d(
x=temp_df['x'],
y=temp_df['y'],
z=temp_df['z'],
customdata=temp_df['time'],
mode='markers',
marker={'size':0.5, 'color':'blue'},
opacity=0.5,
name='oceans',
legendgroup='oceans',
showlegend=False,
hoverinfo='skip')
plotly_data.append(pid_traj)
# BROKOVICH
## mean trajectory
temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
x=temp_df['mean_x'],
y=temp_df['mean_y'],
z=temp_df['mean_z'],
customdata=temp_df['time'],
mode='markers+lines',
marker={'size':2, 'color':'red'},
line={'width':4, 'color':'red'},
name='brokovich',
legendgroup='brokovich',
hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}')
plotly_data.append(mean_traj)
## individual trajectories
for pid in traj_df[traj_df.clip_name=='brokovich'].pid.unique():
temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==pid)]
pid_traj = go.Scatter3d(
x=temp_df['x'],
y=temp_df['y'],
z=temp_df['z'],
customdata=temp_df['time'],
mode='markers',
marker={'size':0.5, 'color':'red'},
opacity=0.5,
name='brokovich',
legendgroup='brokovich',
showlegend=False,
hoverinfo='skip')
plotly_data.append(pid_traj)
# formatting
plotly_layout = go.Layout(showlegend=True,
autosize=False,
width=800,
height=600,
margin={'l':0, 'r':0, 't':35, 'b':0},
legend={'orientation':'h',
'itemsizing':'constant',
'xanchor':'center',
'yanchor':'bottom',
'y':-0.055,
'x':0.5},
title={'text':'Mean and Individual Trajectories for \"oceans\" and \"brokovich\"',
'xanchor':'center',
'yanchor':'top',
'x':0.5,
'y':0.98})
plotly_config = {'displaylogo':False,
'modeBarButtonsToRemove': ['resetCameraLastSave3d','orbitRotation']}
fig_traj = go.Figure(data=plotly_data, layout=plotly_layout)
fig_traj.show(config=plotly_config)
Divergence of a Single Trajectory¶
Total Divergence¶
# compares adjacent distances
def divergence_v1(traj):
length = traj.shape[0]
total_dist = 0
for i in range(1,length):
total_dist += math.sqrt((traj[i,0]-traj[i-1,0])**2 + (traj[i,1]-traj[i-1,1])**2 + (traj[i,2]-traj[i-1,2])**2)
inc = 3 # increment = number of time steps to move forward
dist = np.zeros(length-inc)
for i in range(inc,length):
dist[i-inc] = math.sqrt((traj[i,0]-traj[i-inc,0])**2 + (traj[i,1]-traj[i-inc,1])**2 + (traj[i,2]-traj[i-inc,2])**2)
total_div = 0
for start in range(inc,2*inc):
div = 0
for i in range(start, length-inc, inc):
div += abs(dist[i] - dist[i-inc])
total_div += div
return(total_div / (inc*total_dist))
# compares all distances
def divergence_v2(traj):
length = traj.shape[0]
total_dist = 0
for i in range(1,length):
total_dist += math.sqrt((traj[i,0]-traj[i-1,0])**2 + (traj[i,1]-traj[i-1,1])**2 + (traj[i,2]-traj[i-1,2])**2)
inc = 3 # increment = number of time steps to move forward
dist = np.zeros(length-inc)
for i in range(inc,length):
dist[i-inc] = math.sqrt((traj[i,0]-traj[i-inc,0])**2 + (traj[i,1]-traj[i-inc,1])**2 + (traj[i,2]-traj[i-inc,2])**2)
total_div = 0
for i in range(len(dist)-1):
for j in range(i+1,len(dist)):
total_div += abs(dist[i]-dist[j])
return(total_div / (inc*total_dist))
Now let’s make sure our definition of divergence makes sense for some defined example trajectories.
Work in progress (currently priority #2)
def test_divergence(name):
if (name=='line_constant'): # expected divergence = 0
length = random.randint(50,100)
traj_arr = np.zeros((length,3))
segment_len = random.randint(0,10)
direction = np.random.rand(3)
for i in range(1,length):
for j in range(len(direction)):
traj_arr[i,j] = traj_arr[i-1,j] + segment_len*direction[j]
elif (name=='line_random'): # expected divergence = 0
length = random.randint(50,100)
traj_arr = np.zeros((length,3))
direction = np.random.rand(3)
for i in range(1,length):
segment_len = random.randint(0,10)
for j in range(len(direction)):
traj_arr[i,j] = traj_arr[i-1,j] + segment_len*direction[j]
elif (name=='line_increasing'): # expected divergence = 0
length = random.randint(50,100)
traj_arr = np.zeros((length,3))
direction = np.random.rand(3)
segment_len = random.randint(0,10)
for i in range(1,length):
for j in range(len(direction)):
traj_arr[i,j] = traj_arr[i-1,j] + segment_len*direction[j]
segment_len = segment_len * 1.5
elif (name=='semicircle'): # expected divergence = 0
length = random.randint(50,100)
traj_arr = np.zeros((length,3))
radius = random.random()*50
for i in range(length):
traj_arr[i,0] = radius * math.cos(math.pi*i/length)
traj_arr[i,1] = radius * math.sin(math.pi*i/length)
# elif (name=='curve'):
# elif (name=='zigzag'):
return divergence_v1(traj_arr)
print(f"Divergence of line with constant velocity: {test_divergence('line_constant'):.3f}")
print(f"Divergence of line with random velocity: {test_divergence('line_random'):.3f}")
print(f"Divergence of line with increasing velocity: {test_divergence('line_increasing'):.3f}")
print(f"Divergence of semicicle: {test_divergence('semicircle'):.3f}")
Divergence of line with constant velocity: 0.000
Divergence of line with random velocity: 0.388
Divergence of line with increasing velocity: 0.495
Divergence of semicicle: 0.000
temp_df = traj_df[traj_df.clip_name.isin(['overcome']) & traj_df.pid.isin([1])]
single_traj = np.array(temp_df[['x','y','z']])
print(f"Divergence of single trajectory: {divergence_v1(single_traj):.3f}")
mean_traj = np.array(temp_df[['mean_x','mean_y','mean_z']])
print(f"Divergence of mean trajectory: {divergence_v1(mean_traj):.3f}")
Divergence of single trajectory: 0.365
Divergence of mean trajectory: 0.290
Divergence as a Function of Time¶
Work in progress (currently priority #1)
Need to define a function and create a figure to show how it works. Then I can start testing on defined examples.
Divergence Between Two Trajectories¶
Work in progress (currently priority #3)
Need to define a function and create a figure to show how it works.